# coding: utf-8

"""
transutils.env, the environment manager of transutils.

This module provides transutils commands information about the environment,
like configurations, command line arguments and available translation files.
"""

import codecs
import os
import os.path
import re
import ConfigParser as parser

class ConfigError(Exception): pass

def _safe_get(config, section, option, default=None):
    try:
        return config.get(section, option)
    except (parser.NoSectionError, parser.NoOptionError):
        return default

def _find_project_root():
    curr = os.getcwdu()
    while True:
        if os.path.isdir(os.path.join(curr, u'.transutils')):
            return os.path.normcase(os.path.normpath(curr))
        parent = os.path.dirname(curr)
        if curr == parent:
            return None
        curr = parent

def _load_config(project_root):
    config = parser.SafeConfigParser()
    if project_root is None:
        # Not inside a project tree, so empty config.
        return config
    file = os.path.join(project_root, '.transutils', 'project.conf')
    if not os.path.exists(file):
        return config

    try:
        fp = codecs.open(file, u'r', encoding=u'utf-8')
        config.readfp(fp)
    except IOError:
        # The filename exists, but it's a dir or the user cannot open it.
        raise ConfigError(u'Cannot open config file: %s.' % file)
    except parser.Error as e:
        raise ConfigError(u'Cannot parse config file: %s.' % unicode(e))

    return config

def _get_path_pattern(root, config, option, default, valid_prefix = None):
    if root is None: return None
    pattern = _safe_get(config, u'path-patterns', option, default)
    pattern = pattern.replace(u'\\', u'/')
    pattern = os.path.normcase(os.path.normpath
                               (os.path.join(root, pattern)))

    # The following characters are not allowed in windows filenames.
    # So invalidate them for better cross-platform support.
    pat_nodrive = os.path.splitdrive(pattern)[1]
    for char in u':*?"<>|':
        if char in pat_nodrive:
            raise ConfigError(u'Illegal character "%s" in pattern: %s: %s.' %
                              (char, option, pattern))

    if valid_prefix and not pattern.startswith(valid_prefix):
        raise ConfigError(u'Pattern matches files outside the project: %s: %s' %
                          (option, pattern))
    return pattern

def _get_default_lang(config, default):
    value = _safe_get(config, u'general', u'default-language', default)
    languages = value.split()
    if len(languages) != 1:
        raise ConfigError(u'Only one default language allowed: %s' %
                          languages)
    return languages[0]

def _get_file_path(pattern, textdomain, language):
    if pattern is None: return None
    temp = pattern.replace(u'${textdomain}', textdomain)
    return temp.replace(u'${language}', language)

def _find_available_textdomains(original_pattern, language):
    if original_pattern is None: return None
    textdomains = []
    pattern = original_pattern.replace(u'${language}', language)

    # When the pattern itself is a vaild file path.
    if pattern.find(u'${textdomain}') == -1:
        if os.path.isfile(pattern): return [u'']
        else: return []

    # Now the pattern has the format:
    #    "[parent]/[prefix]${textdomain}[suffix]/[child]",
    # where [parent] is the parent directory of the filesystem entry
    # that has the first ${textdomain} as part of its name, and there
    # may be additional ${textdomain}'s in [child]. Note that [child]
    # can also be empty when ${textdomain} only appears in the filename.
    #
    # It is clear that all ${textdomain}'s should be unified in the
    # final match, and the possible values of the first ${textdomain} is
    # determined by the entries whose absolute path matches:
    #    "[parent]/[prefix]${textdomain}[suffix]"
    #
    # Thus those entries also determine the full paths to the files we
    # want, but still we'll have to test if they are actually available.

    (head, var, tail) = pattern.partition(u'${textdomain}')
    (parent, var, prefix) = head.rpartition(os.sep)
    (suffix, var, child) = tail.partition(os.sep)

    # When matching "[prefix]${textdomain}[suffix]" in [parent],
    # both prefix and suffix should be exactly matched,
    # but they may contain regexp metacharacters, so...

    name_matcher = re.compile(u'^' + re.escape(prefix) +
                              u'(.*)' + re.escape(suffix) + u'$')
    for name in os.listdir(parent):
        match_result = name_matcher.match(name)
        if not match_result: continue
        textdomain = match_result.group(1)
        file_path = pattern.replace('${textdomain}', textdomain)
        if os.path.isfile(file_path):
            textdomains.append(textdomain)
    return textdomains

class Env(object):
    def __init__(self):
        self._root = _find_project_root()
        config = self._config = _load_config(self._root)
        self._default_lang = _get_default_lang(config, u'zh_CN')
        self._patterns = {}
        self._patterns[u'pot'] = _get_path_pattern(self._root, config,
            u'templates', u'../official-pots/${textdomain}/${textdomain}.pot')
        self._patterns[u'po'] = _get_path_pattern(self._root, config,
            u'translations', u'game-packages/${textdomain}/${language}.po', self._root)
        self._patterns[u'mo'] = _get_path_pattern(self._root, config,
            u'binaries', u'mo/translations/${language}/LC_MESSAGES/${textdomain}.mo')

    def available_templates(self, language):
        return _find_available_textdomains(self._patterns[u'pot'], language)

    def available_translations(self, language):
        return _find_available_textdomains(self._patterns[u'po'], language)

    def pofile_path(self, textdomain, language):
        return _get_file_path(self._patterns[u'po'], textdomain, language)

    def potfile_path(self, textdomain, language):
        return _get_file_path(self._patterns[u'po'], textdomain, language)

    @property
    def default_language(self):
        return self._default_lang
